/*
 * This file is part of JSTUN. 
 * 
 * Copyright (c) 2005 Thomas King <king@t-king.de> - All rights
 * reserved.
 * 
 * This software is licensed under either the GNU Public License (GPL),
 * or the Apache 2.0 license. Copies of both license agreements are
 * included in this distribution.
 */

package de.javawi.jstun.header;

import java.util.Iterator;
import java.util.TreeMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.javawi.jstun.attribute.MessageAttribute;
import de.javawi.jstun.attribute.MessageAttributeParsingException;
import de.javawi.jstun.util.Utility;
import de.javawi.jstun.util.UtilityException;

@SuppressWarnings("PMD")
public class MessageHeader implements MessageHeaderInterface {
	/*
	 * 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
	 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | STUN Message Type | Message Length |
	 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Transaction ID
	 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 */
	private static final Logger LOGGER = LoggerFactory.getLogger(MessageHeader.class);
	MessageHeaderType type;
	byte[] id = new byte[16];

	TreeMap<MessageAttribute.MessageAttributeType, MessageAttribute> ma = new TreeMap<MessageAttribute.MessageAttributeType, MessageAttribute>();

	public MessageHeader() {
		super();
	}

	public MessageHeader(final MessageHeaderType type) {
		super();
		setType(type);
	}

	public void setType(final MessageHeaderType type) {
		this.type = type;
	}

	public MessageHeaderType getType() {
		return type;
	}

	public static int typeToInteger(final MessageHeaderType type) {
		if (type == MessageHeaderType.BindingRequest) {
			return BINDINGREQUEST;
		}
		if (type == MessageHeaderType.BindingResponse) {
			return BINDINGRESPONSE;
		}
		if (type == MessageHeaderType.BindingErrorResponse) {
			return BINDINGERRORRESPONSE;
		}
		if (type == MessageHeaderType.SharedSecretRequest) {
			return SHAREDSECRETREQUEST;
		}
		if (type == MessageHeaderType.SharedSecretResponse) {
			return SHAREDSECRETRESPONSE;
		}
		if (type == MessageHeaderType.SharedSecretErrorResponse) {
			return SHAREDSECRETERRORRESPONSE;
		}
		return -1;
	}

	public void setTransactionID(final byte[] id) {
		System.arraycopy(id, 0, this.id, 0, 16);
	}

	public void generateTransactionID() throws UtilityException {
		System.arraycopy(Utility.integerToTwoBytes((int) (Math.random() * 65536)), 0, id, 0, 2);
		System.arraycopy(Utility.integerToTwoBytes((int) (Math.random() * 65536)), 0, id, 2, 2);
		System.arraycopy(Utility.integerToTwoBytes((int) (Math.random() * 65536)), 0, id, 4, 2);
		System.arraycopy(Utility.integerToTwoBytes((int) (Math.random() * 65536)), 0, id, 6, 2);
		System.arraycopy(Utility.integerToTwoBytes((int) (Math.random() * 65536)), 0, id, 8, 2);
		System.arraycopy(Utility.integerToTwoBytes((int) (Math.random() * 65536)), 0, id, 10, 2);
		System.arraycopy(Utility.integerToTwoBytes((int) (Math.random() * 65536)), 0, id, 12, 2);
		System.arraycopy(Utility.integerToTwoBytes((int) (Math.random() * 65536)), 0, id, 14, 2);
	}

	public byte[] getTransactionID() {
		final byte[] idCopy = new byte[id.length];
		System.arraycopy(id, 0, idCopy, 0, id.length);
		return idCopy;
	}

	public boolean equalTransactionID(final MessageHeader header) {
		final byte[] idHeader = header.getTransactionID();
		if (idHeader.length != 16) {
			return false;
		}
		if (idHeader[0] == id[0] && idHeader[1] == id[1] && idHeader[2] == id[2] && idHeader[3] == id[3] &&
				idHeader[4] == id[4] && idHeader[5] == id[5] && idHeader[6] == id[6] && idHeader[7] == id[7] &&
				idHeader[8] == id[8] && idHeader[9] == id[9] && idHeader[10] == id[10] && idHeader[11] == id[11] &&
				idHeader[12] == id[12] && idHeader[13] == id[13] && idHeader[14] == id[14] && idHeader[15] == id[15]) {
			return true;
		} else {
			return false;
		}
	}

	public void addMessageAttribute(final MessageAttribute attri) {
		ma.put(attri.getType(), attri);
	}

	public MessageAttribute getMessageAttribute(final MessageAttribute.MessageAttributeType type) {
		return ma.get(type);
	}

	public byte[] getBytes() throws UtilityException {
		int length = 20;
		Iterator<MessageAttribute.MessageAttributeType> it = ma.keySet().iterator();
		while (it.hasNext()) {
			final MessageAttribute attri = ma.get(it.next());
			length += attri.getLength();
		}
		// add attribute size + attributes.getSize();
		final byte[] result = new byte[length];
		System.arraycopy(Utility.integerToTwoBytes(typeToInteger(type)), 0, result, 0, 2);
		System.arraycopy(Utility.integerToTwoBytes(length - 20), 0, result, 2, 2);
		System.arraycopy(id, 0, result, 4, 16);

		// arraycopy of attributes
		int offset = 20;
		it = ma.keySet().iterator();
		while (it.hasNext()) {
			final MessageAttribute attri = ma.get(it.next());
			System.arraycopy(attri.getBytes(), 0, result, offset, attri.getLength());
			offset += attri.getLength();
		}
		return result;
	}

	public int getLength() throws UtilityException {
		return getBytes().length;
	}

	public void parseAttributes(final byte[] data) throws MessageAttributeParsingException {
		try {
			final byte[] lengthArray = new byte[2];
			System.arraycopy(data, 2, lengthArray, 0, 2);
			int length = Utility.twoBytesToInteger(lengthArray);
			System.arraycopy(data, 4, id, 0, 16);
			byte[] cuttedData;
			int offset = 20;
			while (length > 0) {
				cuttedData = new byte[length];
				System.arraycopy(data, offset, cuttedData, 0, length);
				final MessageAttribute ma = MessageAttribute.parseCommonHeader(cuttedData);
				addMessageAttribute(ma);
				length -= ma.getLength();
				offset += ma.getLength();
			}
		} catch (final UtilityException ue) {
			throw new MessageAttributeParsingException("Parsing error");
		}
	}

	public static MessageHeader parseHeader(final byte[] data) throws MessageHeaderParsingException {
		try {
			final MessageHeader mh = new MessageHeader();
			final byte[] typeArray = new byte[2];
			System.arraycopy(data, 0, typeArray, 0, 2);
			final int type = Utility.twoBytesToInteger(typeArray);
			switch (type) {
				case BINDINGREQUEST:
					mh.setType(MessageHeaderType.BindingRequest);
					LOGGER.debug("Binding Request received.");
					break;
				case BINDINGRESPONSE:
					mh.setType(MessageHeaderType.BindingResponse);
					LOGGER.debug("Binding Response received.");
					break;
				case BINDINGERRORRESPONSE:
					mh.setType(MessageHeaderType.BindingErrorResponse);
					LOGGER.debug("Binding Error Response received.");
					break;
				case SHAREDSECRETREQUEST:
					mh.setType(MessageHeaderType.SharedSecretRequest);
					LOGGER.debug("Shared Secret Request received.");
					break;
				case SHAREDSECRETRESPONSE:
					mh.setType(MessageHeaderType.SharedSecretResponse);
					LOGGER.debug("Shared Secret Response received.");
					break;
				case SHAREDSECRETERRORRESPONSE:
					mh.setType(MessageHeaderType.SharedSecretErrorResponse);
					LOGGER.debug("Shared Secret Error Response received.");
					break;
				default:
					throw new MessageHeaderParsingException("Message type " + type + "is not supported");
			}
			return mh;
		} catch (final UtilityException ue) {
			throw new MessageHeaderParsingException("Parsing error");
		}
	}
}